//
// Created by Administrator on 2021/8/6.
//

#include "Shader/ShaderProgram.h"
#include "FileUtils.h"

ShaderProgram::ShaderProgram() : id_(0) {}

ShaderProgram::ShaderProgram(const ShaderInfo& info)
{
    std::string content;
    const char* data = nullptr;

    FileUtils::LoadFile(info.vertPath, content);
    data = content.c_str();
    GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vShader, 1, &data, nullptr);
    glCompileShader(vShader);
    CheckCompile(vShader);

    FileUtils::LoadFile(info.fragPath, content);
    data = content.c_str();
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fShader, 1, &data, nullptr);
    glCompileShader(fShader);
    CheckCompile(fShader);

    GLuint tcShader = 0, teShader = 0, geoShader = 0;
    if(!info.tcsPath.empty())
    {
        FileUtils::LoadFile(info.tcsPath, content);
        data = content.c_str();
        tcShader = glCreateShader(GL_TESS_CONTROL_SHADER);
        glShaderSource(tcShader, 1, &data, nullptr);
        glCompileShader(tcShader);
        CheckCompile(tcShader);
    }

    if(!info.tesPath.empty())
    {
        FileUtils::LoadFile(info.tesPath, content);
        data = content.c_str();
        teShader = glCreateShader(GL_TESS_EVALUATION_SHADER);
        glShaderSource(teShader, 1, &data, nullptr);
        glCompileShader(teShader);
        CheckCompile(teShader);
    }

    if(!info.geoPath.empty())
    {
        FileUtils::LoadFile(info.geoPath, content);
        data = content.c_str();
        geoShader = glCreateShader(GL_GEOMETRY_SHADER);
        glShaderSource(geoShader, 1, &data, nullptr);
        glCompileShader(geoShader);
        CheckCompile(geoShader);

    }

    id_ = glCreateProgram();
    glAttachShader(id_, vShader);
    glAttachShader(id_, fShader);
    if(tcShader != 0)
        glAttachShader(id_, tcShader);
    if(teShader != 0)
        glAttachShader(id_, teShader);
    if(geoShader != 0)
        glAttachShader(id_, geoShader);
    glLinkProgram(id_);
    CheckCompile(id_);

    glDeleteShader(vShader);
    glDeleteShader(fShader);
    if(tcShader != 0)
        glDeleteShader(tcShader);
    if(teShader != 0)
        glDeleteShader(teShader);
    if(geoShader != 0)
        glDeleteShader(geoShader);
}

ShaderProgram::~ShaderProgram()
{
    id_ = 0;
}

void ShaderProgram::Use() const
{
    glUseProgram(id_);
}

void ShaderProgram::SetFloat(const std::string& name, GLfloat value) const
{
    glUniform1f(glGetUniformLocation(id_, name.c_str()), value);
}

void ShaderProgram::SetVector3(const std::string& name, const GLfloat *value) const
{
    glUniform3fv(glGetUniformLocation(id_, name.c_str()), 1, value);
}

void ShaderProgram::SetVector4(const std::string& name, const GLfloat *value) const
{
    glUniform4fv(glGetUniformLocation(id_, name.c_str()), 1, value);
}

void ShaderProgram::SetMatrix4(const std::string& name, const GLfloat *value) const
{
    glUniformMatrix4fv(glGetUniformLocation(id_, name.c_str()), 1, GL_FALSE, value);
}

GLuint ShaderProgram::id() const
{
    return id_;
}

void ShaderProgram::CheckCompile(GLuint target)
{
    static const GLsizei size = 2048;
    static GLchar infoLog[size];
    static int success = 0;
    if(glIsShader(target))
    {
        glGetShaderiv(target, GL_COMPILE_STATUS, &success);
        if(!success)
        {
            glGetShaderInfoLog(target, size, nullptr, infoLog);
            ERROR(infoLog)
        }
    }
    else if(glIsProgram(target))
    {
        glGetProgramiv(target, GL_LINK_STATUS, &success);
        if(!success)
        {
            glGetProgramInfoLog(target, size, nullptr, infoLog);
            ERROR(infoLog)
        }
    }
}